/**
 * 基于material ui 封装的树组件
 * @params props.treeData 树组件的数据，id为唯一标识符，children为子节点，name为展示的字符, 默认[]
 * @params props.showchecked 是否展示勾选框 默认false
 * @params props.getCheckedNodesChange(checkedNodes) 每当勾选框有所变化，获取当前勾选的节点, 无默认，非必传
 * @params props.isClickLabelCheck 点击label的是否是否要勾选节点， 默认true
 * @params props.getCurrentSelected(nodeData, isChecked) 某个节点勾选框变化的时候，回传父组件当前变化的节点以及勾选状态
 * @params props.changeExpandOnLabel 是否在点击label的时候展开或收起节点，默认false, 若为true, 如果该节点有子节点，则该节点不能选中
 * @params props.onNodeSelect(event, nodeKey) 节点被点击选择的时候，回传函数
 * @params props.getClickedNodeKeys(clickedNodeKeys) 当前点击事件选中的节点key变化时，回传到父组件
 * @params {Array} props.defaultCheckedNodes 在显示勾选框的情况下默认勾选的节点
 * @params {Array} props.defaultClickNodeKeys 默认点击选中状态的子节点
 * @params {Boolean} defaultExpandAll 默认展开全部节点
 * @params {Boolean} checkStrictly 显示勾选框的情况下是否严格执行父子不相关。为true则父子不相关
 * @params {object} proprtyRead:{nodeId: 'id', childrenpropety: 'children', labelpropety: 'name'} 树组件读取的属性
 * @params {function} formatLabel 可以对树组件的label进行格式化
 * @params {Boolean} cancelByClick 是否可以取消高亮节点
 */

/**
 * TreeItem 组件
 * 如果想用单独禁用某个子节点的点击，则在该节点上给disabledItem属性
 * 如果想禁用某个节点并且禁用该节点下面的所有子节点，则在该节点上给disabled属性
 */
import { useState, useCallback, useEffect } from 'react'
import propsType from 'prop-types'
import { TreeView } from '@mui/lab';
import ArrowRightIcon from '@mui/icons-material/ArrowRight';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
// import CustomContent from './CustomContent'
import CustomTreeItem from './CustomTreeItem'
import { searchDataById, deepCopy, unidimensionalList } from '@/utils/util'
import useWatch from '../../Hook/useWatch'

// 寻找某个节点的父节点
const findParentNode = (nodeId, treeData = [], parent = null, keypropety = "id", childrenpropety = "children") => {
  let parentNode = null
  // 深拷贝
  let newArr = deepCopy(treeData)
  for (let item of newArr) {
		if (item[keypropety] === nodeId) {
      parentNode = parent
      break
    }
		if (item[childrenpropety] && item[childrenpropety].length) {
			const res = findParentNode(nodeId, item[childrenpropety], item, keypropety, childrenpropety);
			if (res && Object.keys(res).length) parentNode = res;
		}
	}
  return parentNode
}

const MyTreeView = (props) => {

  const {
    treeData,
    showchecked,
    isClickLabelCheck,
    changeExpandOnLabel,
    defaultExpandAll,
    checkStrictly,
    proprtyRead,
    formatLabel,
    cancelByClick
  } = props

  const [checkedNodes, setCheckedNodes] = useState([])
  const [clickedNodeKeys, setClickedNodeKeys] = useState([])
  // 展开的节点
  const [expanded, setExpanded] = useState([])

  const addSelectedData = useCallback((node, parent, searSelectArr = checkedNodes) => {
    let newArr = deepCopy(searSelectArr)
    for (let item of newArr) {
      if (item[proprtyRead.nodeId] === parent[proprtyRead.nodeId]) {
        item[proprtyRead.childrenpropety].push(node)
      } else if (item[proprtyRead.childrenpropety] && item[proprtyRead.childrenpropety].length) {
        item[proprtyRead.childrenpropety] = deepCopy(addSelectedData(node, parent, item[proprtyRead.childrenpropety]))
      }
    }
    return newArr
  }, [checkedNodes])

  // 勾选了某一项，往已勾选数组中添加数据
  const dealSelectData = useCallback((node, children) => {
    const newArr = deepCopy(checkedNodes)
    // 深拷贝浅拷贝的问题
    let parent = findParentNode(node[proprtyRead.nodeId], treeData, null, proprtyRead.nodeId, proprtyRead.childrenpropety)
    if (parent) {
      parent = deepCopy(parent)
    }
    // const parent = JSON.parse(JSON.stringify(findParentNode(node.id, treeData)))
    if (!parent && !children) { // 如果该节点没有父节点，则说明是根节点，如果没有children则是第一次遍历，说明勾选的就是根节点
      const originIndex = newArr.findIndex(item => {
        return item[proprtyRead.nodeId] === node[proprtyRead.nodeId]
      })
      if (originIndex < 0) { // 如果是根节点并且该根节点原来没有勾选，则直接勾选上
        setCheckedNodes(deepCopy([...newArr, node]))
      } else { // 如果是根节点并且原来已经勾选，则替换新的，避免出现半勾选情况
        newArr[originIndex] = node
        setCheckedNodes(deepCopy(newArr))
      }
      return 
    }
    if (!parent) { // 如果到了根节点，但是有children，说明是从下向上遍历到了根节点，此时需要考虑根节点只塞入一个值的问题
      const selectedRootNode = searchDataById(node[proprtyRead.nodeId], newArr, proprtyRead.nodeId, proprtyRead.childrenpropety)
      if (selectedRootNode) { // 如果当前根节点已经勾选，则
        newArr.forEach(item => {
          item[proprtyRead.childrenpropety].push(JSON.parse(JSON.stringify(children)))
        })
      } else { // 如果当前根节点都没勾选
        node[proprtyRead.childrenpropety] = [children]
        newArr.push(JSON.parse(JSON.stringify(node)))
      }
      setCheckedNodes(deepCopy([...newArr]))
      return
    }
    if (parent) {
      let selectedParent = searchDataById(parent[proprtyRead.nodeId], newArr, proprtyRead.nodeId, proprtyRead.childrenpropety)
      if (selectedParent) { // 如果父节点勾选了,将该node添加进已勾选状态数组
        setCheckedNodes(deepCopy(addSelectedData(node, selectedParent)))
        return
      }
      // 连父节点都没勾选，那就继续向上查找
      dealSelectData(parent, node)
    }
  }, [checkedNodes, addSelectedData])

  // 删除某个已勾选节点
  const removeSelectedData = useCallback((nodeData, arr) => {
    let filterData = arr
    filterData.forEach(item => {
      if (item[proprtyRead.childrenpropety] && item[proprtyRead.childrenpropety].length) {
        item[proprtyRead.childrenpropety] = removeSelectedData(nodeData, item[proprtyRead.childrenpropety])
      }
    })
    filterData = filterData.filter(item => {
      return item[proprtyRead.nodeId] !== nodeData[proprtyRead.nodeId]
    })
    return filterData
  }, [])

  const isRemoveParent = useCallback((nodeData, arr) => {
    let newArr = deepCopy(arr)
    const parent = findParentNode(nodeData[proprtyRead.nodeId], newArr, null, proprtyRead.nodeId, proprtyRead.childrenpropety)
    if (!parent) { // 一路往上找找到了根节点
      newArr = removeSelectedData(nodeData, newArr)
    } else if (parent[proprtyRead.childrenpropety].length > 1) { // 如果长度大于1则直接移除
      newArr = removeSelectedData(nodeData, newArr)
    } else if (parent[proprtyRead.childrenpropety].length === 1) { // 如果长度为1则要继续往上找
      newArr = isRemoveParent(parent, newArr)
    }
    return newArr
  }, [removeSelectedData])

  const checkChange = useCallback((nodeData, isChecked) => {
    // 如果父组件需要拿到当前勾选框变化的节点，则回传出去
    if (props.getCurrentSelected && typeof (props.getCurrentSelected) === 'function') {
      props.getCurrentSelected(nodeData, isChecked)
    }
    if (checkStrictly) { // 如果是父子不相关,则只需要对第一层进行判断即可
      if (isChecked) {
        setCheckedNodes([...checkedNodes, nodeData])
      } else {
        const newArr = checkedNodes.filter(item => {
        return item[proprtyRead.nodeId] !== nodeData[proprtyRead.nodeId]
        })
        setCheckedNodes(newArr)
      }
    } else { // 父子相关，即如果父节点勾选上，子节点也得勾选上
      if (isChecked) {
        dealSelectData(nodeData)
      } else { // 如果是取消勾选
        const parent = findParentNode(nodeData[proprtyRead.nodeId], checkedNodes, null, proprtyRead.nodeId, proprtyRead.childrenpropety)
        if (!parent) { // 如果没有父节点，则直接移除
          let newArr = checkedNodes.filter(item => {
            return item[proprtyRead.nodeId] !== nodeData[proprtyRead.nodeId]
          })
          setCheckedNodes(newArr)
        } else if (parent) { // 如果有父节点
          const newArr2 = isRemoveParent(nodeData, checkedNodes)
          setCheckedNodes(newArr2)
        }
      }
    }
    
  }, [checkedNodes, dealSelectData])

  // 每当勾选的节点有变化，传出去当前勾选的节点数据
  // useEffect(() => {
  //   if (props.getCheckedNodesChange && typeof (props.getCheckedNodesChange) === 'function') {
  //     props.getCheckedNodesChange(checkedNodes)
  //   }
  // }, [checkedNodes, props.getCheckedNodesChange])

  useWatch(checkedNodes, () => {
    if (props.getCheckedNodesChange && typeof (props.getCheckedNodesChange) === 'function') {
      props.getCheckedNodesChange(checkedNodes)
    }
  })

  // 每当点击选中的节点变化时，抛出去当前点击的节点
  useEffect(() => {
    if (props.getClickedNodeKeys && typeof (props.getClickedNodeKeys) === 'function') {
      props.getClickedNodeKeys(clickedNodeKeys)
    }
  }, [clickedNodeKeys, props.getClickedNodeKeys])

  // 节点被点击选择的时候的回传参数
  const onNodeSelect = useCallback((event, nodeKey) => {
    const nodeData = searchDataById(nodeKey, treeData, proprtyRead.nodeId, proprtyRead.childrenpropety)
    // 如果在点击label的时候要求有展开收起功能，则如果该节点有子节点，则该节点不能选中
    if (nodeData && nodeData[proprtyRead.childrenpropety] && changeExpandOnLabel) {
      return
    }
    // 自己封装的组件不会有多选的情况, cancelByClick 表示是否可以取消高亮某个节点
    if (clickedNodeKeys && clickedNodeKeys.length && clickedNodeKeys[0] === nodeKey && cancelByClick) { // 如果当前点击的节点等于当前节点，则取消选中状态
      setClickedNodeKeys([])
    } else {
      setClickedNodeKeys([nodeKey])
    }
    // 回传给父组件
    if (props.onNodeSelect && typeof (props.onNodeSelect) === 'function') {
      const nodeData = searchDataById(nodeKey, treeData, proprtyRead.nodeId, proprtyRead.childrenpropety)
      props.onNodeSelect(event, nodeKey, nodeData)
    }
  }, [clickedNodeKeys, setClickedNodeKeys, props.onNodeSelect])

  // 出现复选框的情况下默认勾选的节点
  useEffect(() => {
    if (showchecked) {
      setCheckedNodes(props.defaultCheckedNodes)
    }
  }, [props.defaultCheckedNodes])

  // 默认点击选中状态的节点
  useEffect(() => {
    setClickedNodeKeys(props.defaultClickNodeKeys)
    // 展开到某个节点, 自己封装的组件不会有多点击情况
    expandNode(props.defaultClickNodeKeys[0])
  }, [props.defaultClickNodeKeys])

  const getExpandNode = useCallback((nodeKey, newExpaned = []) => {
    newExpaned.push(nodeKey)
    const parent = findParentNode(nodeKey, treeData, null, proprtyRead.nodeId, proprtyRead.childrenpropety)
    if (parent) {
      newExpaned = getExpandNode(parent[proprtyRead.nodeId], newExpaned)
    }
    return newExpaned
  }, [treeData, proprtyRead.nodeId, proprtyRead.childrenpropety])

  const expandNode = useCallback((nodeKey) => {
    if (expanded.includes(nodeKey)) { // 如果该节点已经展开
      return
    }
    let arr = getExpandNode(nodeKey)
    if (arr?.length) {
      arr = arr.filter(item => {
        return !expanded.includes(item) && item !== nodeKey
      })
      setExpanded([...expanded, ...arr])
    }
  }, [expanded, getExpandNode])

  // 默认展开全部节点
  useEffect(() => {
    if (defaultExpandAll) {
      const expandArr = unidimensionalList(treeData, [], proprtyRead.childrenpropety)
      if (expandArr?.length) {
        const expandIds = expandArr.map(item => {
        return item[proprtyRead.nodeId]
        })
        setExpanded(expandIds)
      }
    }
  }, [])

  const handleToggle = (_, nodeIds) => {
    setExpanded(nodeIds)
  }

  const renderTree = (nodes) => {
    return nodes.map(item => {
      return (
        <CustomTreeItem
          key={item[proprtyRead.nodeId]}
          nodeId={item[proprtyRead.nodeId]}
          label={formatLabel ? formatLabel(item) : item[proprtyRead.labelpropety]}
          disabledItem={item.disabledItem}
          disabled={item.disabled}
          showchecked={showchecked}
          treeData={treeData}
          checkedNodes={checkedNodes}
          checkChange={checkChange}
          isClickLabelCheck={isClickLabelCheck}
          changeExpandOnLabel={changeExpandOnLabel}
          checkStrictly={checkStrictly}
          keypropety={proprtyRead.nodeId}
          childrenpropety={proprtyRead.childrenpropety}
          labelpropety={proprtyRead.labelpropety}
        >
          {Array.isArray(item[proprtyRead.childrenpropety])
            ? renderTree(item[proprtyRead.childrenpropety])
            : null}
        </CustomTreeItem>
      )
    })
  }

  return (
    <TreeView
      label={proprtyRead.labelpropety}
      className={`myTreeView ${props.className}`}
      defaultCollapseIcon={<ArrowDropDownIcon style={{ fontSize: 24 }} />}
      defaultExpandIcon={<ArrowRightIcon style={{ fontSize: 24 }} />}
      onNodeSelect={onNodeSelect}
      selected={clickedNodeKeys}
      expanded={expanded}
      onNodeToggle={handleToggle}
      sx={{ minWidth: '100%', ...props.style }}
    >
      {renderTree(treeData)}
    </TreeView>
  )
}

MyTreeView.propTypes = {
  treeData: propsType.array,
  showchecked: propsType.bool,
  getSeletedNodes: propsType.func,
  isClickLabelCheck: propsType.bool,
  getCurrentSelected: propsType.func,
  changeExpandOnLabel: propsType.bool,
  onNodeSelect: propsType.func,
  getClickedNodeKeys: propsType.func,
  defaultCheckedNodes: propsType.array,
  defaultClickNodeKeys: propsType.array,
  defaultExpandAll: propsType.bool,
  checkStrictly: propsType.bool,
  proprtyRead: propsType.object,
  formatLabel: propsType.func,
  cancelByClick: propsType.bool
}

MyTreeView.defaultProps = {
  treeData: [],
  showchecked: false,
  isClickLabelCheck: true,
  changeExpandOnLabel: false,
  defaultCheckedNodes: [],
  defaultClickNodeKeys: [],
  defaultExpandAll: true,
  checkStrictly: false,
  proprtyRead: {
    nodeId: 'id',
    childrenpropety: 'children',
    labelpropety: 'name'
  },
  cancelByClick: true
}

export default MyTreeView